﻿/////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2009 Eric Bowen
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
// associated documentation files (the "Software"), to deal in the Software without restriction, including 
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 
// following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial 
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Author's blog: http://blog.scrappydog.com
// Updated source code available:  http://scrappydb.codeplex.com
/////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Web.Caching;
using Amazon.SimpleDB.Model;
using NLog;
using ScrappyDB.Utilities;

namespace ScrappyDB.BaseClasses
{
    public class SdbCollection<T> : Collection<T> where T : class, new()
    {
        // Create a logger for use in this class
        private static readonly Logger Log = LogManager.GetCurrentClassLogger();

        public DbStatus CollectionStatus = DbStatus.Unknown;
        private IDb _db;

        private Cache _webCache;

        public SdbCollection()
        {
        }

        public SdbCollection(Cache cache)
        {
            WebCache = cache;
        }

        public Cache WebCache
        {
            get { return _webCache; }
            set
            {
                _webCache = value;

                if (Db != null)
                    Db.WebCache = _webCache;

                if (typeof (T).IsSubclassOf(typeof (SdbEntity)))
                {
                    PropertyInfo webCacheProperty = typeof (T).GetProperty("WebCache");
                    //pass the cache goodness on to child objects
                    foreach (T item in Items)
                    {
                        webCacheProperty.SetValue(item, _webCache, null);
                    }
                }
            }
        }

        public IDb Db
        {
            get { return _db ?? (_db = new Repository(WebCache).DB()); }
            set
            {
                _db = value;

                if (!typeof (T).IsSubclassOf(typeof (SdbEntity)))
                    return;

                PropertyInfo dbProperty = typeof (T).GetProperty("Db");
                //set the db reference for all child objects too
                foreach (T entity in this)
                {
                    dbProperty.SetValue(entity, _db, null);
                }
            }
        }

        //paging related properties
        public string NextToken { get; set; }
        public int StartPosition { get; set; }
        public int RowsToReturn { get; set; }

        public bool? Dirty
        {
            get
            {
                if (typeof (T).IsSubclassOf(typeof (SdbEntity)))
                {
                    PropertyInfo dirtyProperty = typeof (T).GetProperty("Dirty");
                    foreach (T o in this)
                    {
                        if (o != null && (bool) dirtyProperty.GetValue(o, null))
                            return true;
                    }
                    return false;
                }
                return null;
            }
            set
            {
                if (!typeof (T).IsSubclassOf(typeof (SdbEntity)))
                    throw new Exception("Dirty is only support on classes that inherit 'SdbEntity'");

                PropertyInfo dirtyProperty = typeof (T).GetProperty("Dirty");
                foreach (T o in this)
                {
                    if (o != null)
                        dirtyProperty.SetValue(o, value, null);
                }
            }
        }

        public bool IsLazyLoadEnabled
        {
            get
            {
                if (!typeof (T).IsSubclassOf(typeof (SdbEntity)))
                    throw new Exception("IsLazyLoadEnabled is only support on classes that inherit 'SdbEntity'");

                PropertyInfo isLazyLoadEnabledProperty = typeof (T).GetProperty("IsLazyLoadEnabled");

                foreach (T o in this)
                {
                    if (o != null && (bool) isLazyLoadEnabledProperty.GetValue(o, null))
                        return true;
                }
                return false;
            }
            set
            {
                if (!typeof (T).IsSubclassOf(typeof (SdbEntity)))
                    throw new Exception("IsLazyLoadEnabled is only support on classes that inherit 'SdbEntity'");

                PropertyInfo isLazyLoadEnabledProperty = typeof (T).GetProperty("IsLazyLoadEnabled");

                foreach (T o in this)
                {
                    if (o != null)
                        isLazyLoadEnabledProperty.SetValue(o, value, null);
                }
            }
        }

        public void QueryCollection(string query, bool consitentRead)
        {
            QueryCollection(query, 0, null, consitentRead);
        }

        public void QueryCollection(string query, Cache cache, bool consitentRead)
        {
            QueryCollection(query, 0, cache, consitentRead);
        }

        public void QueryCollection(string query, int limit, bool consitentRead)
        {
            QueryCollection(query, limit, null, consitentRead);
        }

        public void QueryCollection(string query, int limit, Cache cache, bool consitentRead)
        {
            WebCache = cache;

            foreach (T p in Db.Query<T>(query, null, 0, limit, consitentRead))
            {
                Add(p);
            }
        }

        public void QueryBasedOnRelatedEntity<TE>(TE o)
        {
            //var db = new ScrappyDB(DomainName);

            //string relatedEntityName = o.GetType().Name;
            //var idProperty = o.GetType().GetProperty(relatedEntityName + "Id");

            //string query = "['" + relatedEntityName + "Id' = '" + idProperty.GetValue(o, null).ToString() + "'] intersection ['EntityName' = '" + relatedEntityName + "_" + EntityName + "_RELATIONSHIP']";

            ////_addressCollection.Query(query);

            //var qarequest = new QueryWithAttributesRequest { DomainName = DomainName, QueryExpression = query };
            //QueryWithAttributesResponse response = db.Service.QueryWithAttributes(qarequest);


            //if (response.IsSetQueryWithAttributesResult())
            //{
            //    QueryWithAttributesResult queryWithAttributesResult = response.QueryWithAttributesResult;
            //    List<Item> itemList = queryWithAttributesResult.Item;
            //    foreach (var item in itemList)
            //    {
            //        List<Attribute> attributeList = item.Attribute;
            //        foreach (var attribute in attributeList)
            //        {

            //            if (attribute.IsSetName() && attribute.Name == EntityName + "Id")
            //            {
            //                if (attribute.IsSetValue())
            //                {
            //                    var a = new T();
            //                    this.Add(db.Load<T>(a, attribute.Value));
            //                }
            //            }
            //        }
            //    }
            //}
        }

        public T GetById(string id)
        {
            foreach (T item in Items)
            {
                string itemId = EntityHelper.GetEncodedItemNameValue(item);
                if (id == itemId)
                    return item;
            }
            return null;
        }

        public void AddById(string id)
        {
            AddById(id, null);
        }

        public void AddById(string id, Cache cache)
        {
            Db.WebCache = cache;

            Add(Db.Find<T>(id));
        }

        public void SaveChanges()
        {
            InternalSave();
        }

        public void SaveChanges(Cache cache)
        {
            WebCache = cache;
            InternalSave();
        }

        private void InternalSave()
        {
            if (Count == 0)
                return;

            int batchCount = 0;
            BatchPutAttributesRequest batchRequest = Db.CreateBatchRequest(this[0]);

            foreach (T o in this)
            {
                bool dirty = true;

                if (typeof (T).IsSubclassOf(typeof (SdbEntity)))
                {
                    PropertyInfo dbProperty = typeof (T).GetProperty("Db");
                    if (dbProperty == null)
                        throw new Exception("Bad property name?");

                    PropertyInfo dirtyProperty = typeof (T).GetProperty("Dirty");
                    if (dirtyProperty == null)
                        throw new Exception("Bad property name?");

                    dbProperty.SetValue(o, Db, null);
                    dirty = (bool) dirtyProperty.GetValue(o, null);
                }
                if (dirty)
                {
                    batchCount++;


                    ReplaceableItem batchItem = Db.CreateBatchRequestItem(o);
                    batchRequest.WithItem(batchItem);

                    if (batchCount == 25)
                    {
                        try
                        {
                            Db.BatchPut(batchRequest);
                        }
                        catch (Exception ex)
                        {
                            Log.Error("ERROR: ScrappyDbCollection<T>.Save: BatchPut - {0}",
                                      ex.Message);
                        }

                        batchRequest = Db.CreateBatchRequest(this[0]);
                        batchCount = 0;
                    }
                }
            }

            if (batchRequest.Item.Count > 0)
                Db.BatchPut(batchRequest);
        }
    }
}